<?php

namespace App\Service\Common;

use App\Exceptions\Custom\CustomException;
use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Type;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Writer\CSV\Writer;
use Box\Spout\Writer\Exception\WriterNotOpenedException;
use Illuminate\Support\Str;
use Kuafu\File\FileUtil;
use OSS\Core\OssException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

class ExportService
{

    /**
     * 导出文件名
     *
     * @var string
     */
    public string $fileName;

    /**
     * 导出头部
     *
     * @var array
     */
    public array $header;

    /**
     * 本地文件路径
     *
     * @var string
     */
    public string $fileLocalPath;

    /**
     * 本地文件写入句柄
     *
     * @var Writer
     */
    public Writer $writer;

    /**
     * 导出文件后缀名
     *
     * @var string
     */
    public string $suffix;

    /**
     * 上传完成是否删除源文件
     *
     * @var bool
     */
    public bool $deleteLocalFile = false;

    /**
     * @param string $fileName
     * @param array $header
     * @param string $suffix
     * @throws ContainerExceptionInterface
     * @throws IOException
     * @throws NotFoundExceptionInterface
     * @throws WriterNotOpenedException
     */
    public function __construct(string $fileName, array $header, string $suffix = "csv")
    {
        $this->fileName      = $fileName;
        $this->header        = $header;
        $this->suffix        = $suffix;
        $this->fileLocalPath = self::generateFile($fileName, $suffix);
        $this->writer        = self::generateWriter($this->fileLocalPath);
        // 写入头部信息
        $this->writer->addRow(WriterEntityFactory::createRowFromArray($this->header));
        return $this;
    }

    /**
     * 写入内容
     *
     * @param array $contents
     * @return ExportService
     * @throws IOException
     * @throws InvalidArgumentException
     * @throws WriterNotOpenedException
     */
    public function writeContents(array $contents): ExportService
    {
        $this->writer->addRows(collect($contents)->map(function (array $row) {
            return WriterEntityFactory::createRowFromArray($row);
        })->values()->toArray());

        return $this;
    }

    /**
     * oss 上传
     *
     * @return array
     * @throws OssException
     */
    public function ossExport(): array
    {
        $OssObjectName = "tmp/" . date('Ymd') . "/" . Str::random() . "/" . $this->fileName . ".{$this->suffix}";
        return FileUtil::uploadFile(
            $this->fileLocalPath,
            $this->suffix,
            $OssObjectName
        );
    }

    /**
     * @param bool $deleteLocalFile
     * @return $this
     */
    public function setDeleteLocalFile(bool $deleteLocalFile = false): ExportService
    {
        $this->deleteLocalFile = $deleteLocalFile;
        return $this;
    }

    /**
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function __destruct()
    {
        // TODO: Implement __destruct() method.
        if ($this->deleteLocalFile) {
            $fileSystem = getFileSystem();
            if ($fileSystem->exists($this->fileLocalPath)) {
                $fileSystem->delete($this->fileLocalPath);
            }
        }
    }

    /**
     * 生成本地文件路径
     *
     * @param string $fileName
     * @param string $suffix
     * @return string
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public static function generateFile(string $fileName, string $suffix = "csv"): string
    {
        $fileName       = str_replace([" ", "%", "+", "!"], "", $fileName);
        $exportFilename = vsprintf("%s-%s.%s", [$fileName, date("Y-m-d-H-i", time()), $suffix]);

        $localDir   = getTmpDir("export/" . formatDateYmd(time()));
        $fileSystem = getFileSystem();
        if (!$fileSystem->isDirectory($localDir)) {
            $fileSystem->makeDirectory($localDir, 0777, true);
        }
        return $localDir . "/" . $exportFilename;
    }

    /**
     * @param string $fileLocalPath
     * @return Writer
     * @throws IOException
     */
    public static function generateWriter(string $fileLocalPath): Writer
    {
        $writer = WriterEntityFactory::createCSVWriter();
        $writer->openToFile($fileLocalPath);
        return $writer;
    }

    /**
     * 导出文件上传至oss
     *
     * @param string $fileName 导出文件名 不带后缀
     * @param array $header 导出文件头部信息
     * @param array $contents 导出文件数据
     * @param string $suffix 导出文件名后缀 默认csv
     * @return array
     * @throws ContainerExceptionInterface
     * @throws IOException
     * @throws InvalidArgumentException
     * @throws NotFoundExceptionInterface
     * @throws WriterNotOpenedException
     * @throws OssException
     */
    public static function export(string $fileName, array $header, array $contents, string $suffix = "csv"): array
    {
        $fileName       = str_replace([" ", "%", "+", "!"], "", $fileName);
        $exportFilename = vsprintf("%s-%s.%s", [$fileName, date("Y-m-d-H-i", time()), $suffix]);

        $localDir   = getTmpDir("export/" . formatDateYmd(time()));
        $fileSystem = getFileSystem();
        if (!$fileSystem->isDirectory($localDir)) {
            $fileSystem->makeDirectory($localDir, 0777, true);
        }
        $fileLocalPath = $localDir . "/" . $exportFilename;

        switch (strtolower($suffix)){
            case Type::CSV:
                $writer = WriterEntityFactory::createCSVWriter();
                break;
            case Type::XLSX:
                $writer = WriterEntityFactory::createXLSXWriter();
                break;
            default:
                throw new CustomException('未知的导出类型');
        }
        $writer->openToFile($fileLocalPath);
        $writer->addRow(WriterEntityFactory::createRowFromArray($header));
        $addContents = function ($contents){
            foreach ($contents as $content){
                yield WriterEntityFactory::createRowFromArray($content);
            }
        };
        foreach ($addContents($contents) as $contentRow){
            $writer->addRow($contentRow);
        }
        $writer->close();//xlsx不加close导出没数据
        unset($contents);

        $OssObjectName = "tmp/" . date('Ymd') . "/" . Str::random() . "/" . $fileName . ".{$suffix}";
        return FileUtil::uploadFile(
            $fileLocalPath,
            $suffix,
            $OssObjectName
        );
    }

}
